// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package parser

import (
	"bytes"
	"fmt"
	"github.com/cznic/strutil"
	"go/scanner"
	"go/token"
	"log"
	"unicode/utf8"
)

type xErr struct {
	states []int
	last   int
	msg    string
}

var xErrs []xErr

var TokSyms = map[string]string{
	" %":                 "%",
	" &":                 "&",
	" (":                 "(",
	" )":                 ")",
	" *":                 "*",
	" +":                 "+",
	" -":                 "-",
	" /":                 "/",
	" ^":                 "^",
	" |":                 "|",
	"LANDAND":            "&&",
	"LANDNOT":            "&^",
	"LBODY":              "{",
	"LBREAK":             "break",
	"LCASE":              "case",
	"LCHAN":              "chan",
	"LCOLAS":             ":=",
	"LCOMM":              "<-",
	"LCONST":             "const",
	"LCONTINUE":          "continue",
	"LDDD":               "...",
	"LDEC":               "--",
	"LDEFAULT":           "default",
	"LDEFER":             "defer",
	"LELSE":              "else",
	"LEQ":                "==",
	"LFALL":              "fallthrough",
	"LFOR":               "for",
	"LFUNC":              "func",
	"LGE":                ">=",
	"LGO":                "go",
	"LGOTO":              "goto",
	"LGT":                ">",
	"LIF":                "if",
	"LIGNORE":            "LIGNORE",
	"LIMPORT":            "import",
	"LINC":               "++",
	"LINTERFACE":         "interface",
	"LLE":                "<=",
	"LLSH":               "<<",
	"LLT":                "<",
	"LMAP":               "map",
	"LNE":                "!=",
	"LOROR":              "||",
	"LPACKAGE":           "package",
	"LRANGE":             "range",
	"LRETURN":            "return",
	"LRSH":               ">>",
	"LSELECT":            "select",
	"LSTRUCT":            "struct",
	"LSWITCH":            "switch",
	"LTYPE":              "type",
	"LVAR":               "var",
	"NotPackage":         "NotPackage",
	"NotParen":           "NotParen",
	"PreferToRightParen": "PreferToRightParen",
	"i_LASOP":            "assign operator",
	"sym_LNAME":          "identifier",
	"val_LLITERAL":       "literal",
}

//TODO-
func FakeCompile(file *token.File, src []byte, removeMultiples bool) (err error) {
	lx := newLexer(file, src, universe.new(PackageScope), nil)

	defer func() {
		if e := recover(); e != nil {
			err = e.(error)
			lx.Error(err.Error())
			err = lx.errorList(removeMultiples)
		}
		if len(err.(scanner.ErrorList)) == 0 {
			err = nil
		}
	}()

	if yyParse(lx) != 0 || lx.errored {
		err = lx.errorList(removeMultiples)
	}
	return
}

type lexer struct {
	current byte
	file    *token.File
	base    int // file.Base()

	errored bool
	errors  scanner.ErrorList

	inject  bool      // ';'
	pos     int       // Current offset
	pos0    int       // Returned token start offset
	spos0   int       // dtto for "strings"
	lbrpos  token.Pos // last '{' offset
	scope   *Scope    // The current one
	src     []byte
	srclen  int
	iota    int     // const decl iota current value
	top     []*Node // top level declarations
	pkg     string  // package pkg
	ctype   ValTag
	asoptok int

	lstk     []bool // see src/cmd/gc/lex.c
	loophack bool
	last     int
	prev     int

	curfn     *Node // closures
	funcdepth int
}

func newLexer(file *token.File, src []byte, scope *Scope, top []*Node) *lexer {
	if scope == nil || scope.Tag != PackageScope {
		internalError()
	}

	if len(src) == 0 {
		src = []byte{0}
	}
	file.AddLine(0)
	if src[0] == '\n' {
		file.AddLine(1)
	}
	return &lexer{
		current: src[0],
		file:    file,
		base:    file.Base(),
		scope:   scope.new(FileScope),
		top:     top,
		src:     src,
		srclen:  len(src),
	}
}

func (l *lexer) getc() (c byte) {
	pos := l.pos + 1
	switch {
	case pos < l.srclen:
		c = l.src[pos]
		l.pos = pos
	default:
		l.pos = l.srclen
	}
	l.current = c
	if c == '\n' {
		l.file.AddLine(pos + 1)
	}
	return
}

func (l *lexer) getRune() (c rune) {
	if r, size := utf8.DecodeRune(l.src[l.pos:]); size != 0 {
		l.pos += size
		c = r
	}
	if c == '\n' {
		l.file.AddLine(l.pos + 1)
	}
	return
}

func (l *lexer) newScope(tag ScopeTag) *Scope {
	l.scope = l.scope.new(tag)
	return l.scope
}

func (l *lexer) popScope() *Scope {
	l.scope = l.scope.Outer
	return l.scope
}

func (l *lexer) errorList(removeMultiples bool) scanner.ErrorList {
	if len(l.errors) == 0 {
		return nil
	}

	l.errors.Sort()
	if removeMultiples {
		l.errors.RemoveMultiples()
	}
	return l.errors
}

func (l *lexer) error(pos token.Pos, e string, args ...interface{}) {
	l.errored = true
	l.errors.Add(l.file.Position(pos), fmt.Sprintf(e, args...))
}

func (l *lexer) Error(e string) {
	l.error(token.Pos(l.pos+l.base), e)
}

var XErrorHook func(yyS []int, lastTok int)

func (l *lexer) xError(yyS []yySymType, yyp int) {
	if XErrorHook != nil {
		l.errored = true
		a := make([]int, yyp+1)
		for i, _ := range a {
			a[i] = yyS[yyp-i].yys
		}
		XErrorHook(a, l.last)
		return
	}

search:
	for _, v := range xErrs {
		if v.last >= 0 && v.last != l.last {
			continue
		}

		for i, state := range v.states {
			if state != yyS[yyp-i].yys {
				continue search
			}
		}

		l.error(token.Pos(l.pos0+l.base), v.msg)
		return
	}

	var s string
	switch v := l.last; {
	case v == 0:
		return
	case v < 127:
		s = fmt.Sprintf("%q", v)
		s = s[1 : len(s)-1]
	default:
		s = TokSyms[yyToknames[v-0xe002]]
	}

	l.error(token.Pos(l.pos0+l.base), "syntax error: unexpected %s (%d xErrs. last %d prev %d yys %d)", s, len(xErrs), l.last, l.prev, yyS[yyp].yys) //TODO -debug info
}

func (l *lexer) Lex(lval *yySymType) (y int) {
	y = l.lex(lval)
	switch y {
	case '(', '[':
		if l.loophack || len(l.lstk) != 0 {
			l.lstk = append(l.lstk, l.loophack)
			l.loophack = false
		}
	case ')', ']':
		if n := len(l.lstk) - 1; n >= 0 {
			l.loophack = l.lstk[n]
			l.lstk = l.lstk[:n]
		}
		l.inject = true
	case '{':
		if l.loophack {
			l.loophack = false
			y = LBODY
		}
		l.lbrpos = lval.pos
	case i_LASOP:
		lval.i = l.asoptok
	case LFOR, LIF, LSWITCH, LSELECT:
		l.loophack = true
	}
	return
}

func (l *lexer) lex(lval *yySymType) (y int) {
again:
	y = l.scan()
	lval.pos = token.Pos(l.pos0 + l.base)
	switch y {
	case LIGNORE:
		goto again
	case 0:
		if l.inject {
			y = ';'
			l.inject = false
		}
	case '\n':
		if !l.inject {
			goto again
		}

		y = ';'
		l.inject = false
	case sym_LNAME:
		lval.sym = &Sym{Pos: lval.pos, Name: l.src[l.pos0:l.pos], Scope: l.scope}
		l.inject = true
	case val_LLITERAL:
		lval.val = &Val{Pos: lval.pos, Tag: l.ctype, Val: l.src[l.pos0:l.pos]}
		l.inject = true
	case LBREAK, LCONTINUE, LFALL, LRETURN, LINC, LDEC, ')', ']', '}':
		l.inject = true
	default:
		l.inject = false
	}
	//TODO-
	//switch {
	//case y < 127:
	//fmt.Printf("lex %s %d %q\n", l.file.Position(lval.pos), y, string(y))
	//default:
	//fmt.Printf("lex %s %s\n", l.file.Position(lval.pos), yyToknames[y-0xe002])
	//}
	l.prev, l.last = l.last, y // lex.c:1467
	return
}

func (l *lexer) fixlbrace(lbr int) {
	if lbr == LBODY {
		l.loophack = true
	}
}

func (l *lexer) funchdr(n *Node) {
	l.newScope(OtherScope)
	l.funcdepth++
	n.Outer, l.curfn = l.curfn, n
}

func (l *lexer) funcbody(n *Node) {
	l.popScope()
	l.funcdepth--
	l.curfn = n.Outer
}

func (l *lexer) closurehdr(n *Node) {
	l.funchdr(n)
}

func (l *lexer) closurebody(n []*Node) (y *Node) {
	y = l.curfn
	y.LL = n
	l.funcbody(y)
	return
}

func (l *lexer) checkarglist(all []*Node, input bool) (y []*Node, dcl func()) {
	y, dcl = all, nop

	if len(all) == 0 {
		return
	}

	var named bool
	var bad, oxdot *Node
	for i, v := range all {
		switch v.Op {
		case ONAME:
			// nop
		case OXDOT:
			if oxdot == nil {
				oxdot = v
			}
		case OKEY:
			named = true
			if v.NL.Op == OXDOT && oxdot == nil {
				oxdot = v
			}
			if v.NR.Op == ODDD {
				if i == len(all)-1 { // pos ok, check if alone
					if len(all) == 1 || all[i-1].Op == OKEY {
						break // OK
					}
				}

				l.error(v.NR.Pos, "can only use ... as final argument in list")
			}
		case ODDD:
			if !input {
				l.error(v.Pos, "cannot use ... in output argument list")
			}
		default:
			if bad == nil {
				bad = v
			}
		}
	}

	if named {
		if bad != nil {
			l.error(bad.Pos, "mixed named and unnamed function parameters")
		}

		if oxdot != nil {
			l.error(oxdot.Pos, "cannot declare name %s", oxdot)
		}

		if n := all[len(all)-1]; n.Op != OKEY {
			l.error(n.Pos, "mixed named and unnamed function parameters")
		}

		dcl = func() {
			for _, v := range all {
				switch v.Op {
				case OKEY:
					if n := v.NL; n.Op == ONAME {
						l.declare(n.Name, SymParam)
					}
				case ONAME:
					l.declare(v.Name, SymParam)
				}
			}
		}
	}

	return
}

func (l *lexer) colasdefn(left []*Node) {
	var nnew, nerr int
	for _, x := range left {
		switch x.Op {
		case ORECV, OXDOT:
			nerr++
			l.error(x.Pos, "non-name %s on left side of :=", x)
		case OPAREN:
			switch y := x.NL; y.Op {
			case ONAME:
				nerr++
				l.error(y.Pos, "non-name (%s) on left side of :=", y)
			default:
				internalError(NodeOps[y.Op])
			}
		case ONAME:
			if sym := l.scope.lookup(string(x.Name.Name)); sym == nil {
				nnew++
				l.declare(x.Name, SymVar)
			}
		default:
			internalError(NodeOps[x.Op])
		}
	}
	if nnew == 0 && nerr == 0 {
		l.error(left[0].Pos, "no new variables on left side of :=")
	}
}

func (l *lexer) colas(left, right []*Node) *Node {
	l.colasdefn(left)
	return &Node{Pos: left[0].Pos, Op: OAS2, LL: left, LR: right, IsColas: true}
}

//TODO-
func (l *lexer) sentinel(item interface{}) {
	buf := &bytes.Buffer{}
	fset := token.NewFileSet()
	file := fset.AddFile(l.file.Name(), fset.Base(), len(l.src))
	file.SetLinesForContent(l.src)
	PrintAST(fset, strutil.IndentFormatter(buf, ". "), "sentinel\n", item)
	log.Println(buf)
}

func (l *lexer) declare(sym *Sym, tag SymTag) *Node {
	sym.Scope, sym.Tag = l.scope, tag
	switch b := sym.Name; {
	case
		len(b) == 1 && b[0] == '_',
		tag == SymFunc && l.scope.Tag == FileScope && len(b) == 4 &&
			b[0] == 'i' && b[1] == 'n' && b[2] == 'i' && b[3] == 't':
		// ignore
	default:
		n := string(b)
		if alt := l.scope.insert(n, sym); alt != nil {
			l.error(sym.Pos, "%s redeclared, previous declaration at %s", n, l.file.Position(alt.Pos))
		}
		//l.scope.dump()
	}
	return &Node{Pos: sym.Pos, Op: ONAME, Name: sym}
}

func nop() {}

func (l *lexer) methodname(sym *Sym, typ *Node) (y *Sym) {
	oind := typ.Op == OIND
	if oind {
		typ = typ.NL
	}
	y = &Sym{Pos: sym.Pos, Tag: SymMeth}
	if typ.Op == ONAME {
		if oind {
			y.Name = []byte(fmt.Sprintf("(*%s).%s", typ.Name.Name, sym.Name))
			//fmt.Printf("methodname: oind name %s\n", y.Name)
			return
		}

		y.Name = []byte(fmt.Sprintf("%s.%s", typ.Name.Name, sym.Name))
		//fmt.Printf("methodname: name %s\n", y.Name)
		return
	}

	y.Name = []byte(fmt.Sprintf("p%p.%s", typ, sym.Name)) // bad named type
	//fmt.Printf("methodname: bad name %s\n", y.Name)
	return
}
